package cn.com.netedge.iot.frontend.comm.utils;

import cn.com.netedge.iot.frontend.comm.constants.AppConstant;
import cn.com.netedge.iot.frontend.comm.constants.ChannelConstant;
import cn.com.netedge.iot.frontend.comm.nio.MessageValidator;
import cn.com.netedge.iot.frontend.comm.protocol.LoginResult;
import cn.com.netedge.iot.frontend.comm.protocol.ProtocolDlms;
import cn.com.netedge.iot.frontend.comm.service.DeviceService;
import com.alibaba.fastjson2.JSON;
import cn.com.netedge.iot.common.constants.CacheKeyConstants;
import cn.com.netedge.iot.common.constants.MqConstants;
import cn.com.netedge.iot.common.dataobject.DeviceInfo;
import cn.com.netedge.iot.common.dataobject.DeviceRunningInfo;
import cn.com.netedge.iot.common.dataobject.ReceiveFrame;
import cn.com.netedge.iot.common.enums.MessageDirectEnum;
import cn.com.netedge.iot.common.enums.OnlineStatusEnum;
import cn.com.netedge.iot.common.utils.DateUtil;
import cn.com.netedge.iot.common.utils.RedisUtil;
import cn.com.netedge.iot.common.utils.SpringContextUtil;
import cn.com.netedge.iot.common.utils.StringUtil;
import cn.com.netedge.iot.frontend.comm.mq.UpMessageProducer;
import cn.com.netedge.iot.model.achv.MeterModel;
import cn.com.netedge.iot.model.online.DataCOnlineModel;
import cn.com.netedge.iot.protocol.dlms.asn1.axdr.AxdrLength;
import cn.com.netedge.iot.protocol.dlms.base.AuthenticationMechanism;
import cn.com.netedge.iot.protocol.dlms.base.SecuritySuite;
import cn.com.netedge.iot.protocol.dlms.base.core.security.crypto.GcmModule;
import cn.com.netedge.iot.protocol.dlms.service.impl.MeterDeviceServiceImpl;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Component
public class DecoderUtil {
    private static final Logger logger = LoggerFactory.getLogger(DecoderUtil.class);
    private static final int ONLINE = 1;
    private static final int DEVICE_TYPE_GATEWAY = 1;

    private static DeviceService staticDeviceService;
    private static UpMessageProducer staticUpMessageProducer;

    @Autowired
    private DeviceService deviceService;
    @Autowired
    private UpMessageProducer upMessageProducer;

    @PostConstruct
    public void init() {
        staticDeviceService = deviceService;
        staticUpMessageProducer = upMessageProducer;
    }

    private static String getIp(ChannelHandlerContext ctx) {
        InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
        return socketAddress.getAddress().getHostAddress();
    }

    /**
     * 检查报文是否合法
     */
    public static ReceiveFrame decode(ChannelHandlerContext ctx, ByteBuf inByteBuf) {
        ByteBuf duplicated = inByteBuf.duplicate();
        int readableBytes = duplicated.readableBytes();
        if (readableBytes <= 0) {
            logger.warn("readableBytes = 0, don't need validate");
            return null;
        }
        ReceiveFrame frame = validateMsg(duplicated);
        if(frame == null) {
            return null;
        }
        String channelId = ctx.channel().id().asLongText();
        DeviceInfo deviceInfo = getDeviceInfo(channelId);
        if(deviceInfo != null) {
            frame.setDeviceNumber(deviceInfo.getDeviceNumber());
            frame.setDeviceType(deviceInfo.getDeviceType());
        }
        frame.setIp(getIp(ctx));
        return frame;
    }

    private static ReceiveFrame validateMsg(ByteBuf inByteBuf) {
        ReceiveFrame frame = MessageValidator.validateMsg(inByteBuf);
        if (frame != null) {
            frame.setReadIndex(inByteBuf.readerIndex());
        }
        return frame;
    }


    private static LoginResult handleLogin(byte[] inByteBuf) {
        LoginResult result = ProtocolDlms.handleLogin(inByteBuf);
        return result;
    }

    public static byte[] processFrame(ReceiveFrame frame, String channelId) {
        logger.debug("MessageDealHandler decode channelId:" + channelId);

        String deviceNumber;
        int deviceType;
        String ip = frame.getIp();
        //获取端口号
        Channel channel = ChannelConstant.CHANNEL_MAP.get(channelId);
        logger.debug("channelId:{} ", channelId);
        LoginResult loginResult = handleLogin(frame.getReceiveBuffer());
        //发送响应帧
        byte[] reply = loginResult.getReply();
        if (loginResult.isPass()) {
            deviceNumber = loginResult.getDeviceNumber();
            deviceType = loginResult.getDeviceType();
            putChannelId(deviceNumber, deviceType, channelId);

            //更新通道与终端的关系
            DeviceInfo deviceInfo = putDeviceInfo(deviceNumber, deviceType, channelId);
            if (deviceInfo == null) {
                logger.error("devcieNumber:{} deviceType:{} not found", deviceNumber, deviceType);
                String key = CacheKeyConstants.KEY_DEVICE_NOT_EXISTS + deviceNumber + "_" + deviceType;
                RedisUtil.incr(key);
                RedisUtil.expire(key, 60 * 5); //5分钟失效
                return reply;
            }

            int deviceId = deviceInfo.getDeviceId();
            updateDeviceRunInfo(deviceId, deviceNumber, deviceType, ip, true);

        } else {
            DeviceInfo deviceInfo = getDeviceInfo(channelId);
            if (deviceInfo == null) {
                logger.error("channelId:{}'s device not found", channelId);
                return reply;
            }

            if (deviceInfo != null) {
                deviceNumber = deviceInfo.getDeviceNumber();
                deviceType = deviceInfo.getDeviceType();
                int deviceId = deviceInfo.getDeviceId();
                updateDeviceRunInfo(deviceId, deviceNumber, deviceType, ip, false);
                //处理集中器报文解密
                if(DEVICE_TYPE_GATEWAY == deviceType) {
                    frame.setReceiveBuffer(decryptMessage(frame));
                }
                staticUpMessageProducer.sendUpMessage(deviceNumber, deviceType, JSON.toJSONString(frame));
                //保证终端串行执行
                ChannelConstant.SEND_FLAG_MAP.put(deviceNumber + "_" + deviceType, 0L);
            }
        }
        return reply;
    }

    private static byte[] decryptMessage(ReceiveFrame frame) {
        byte[] msg = frame.getRawData();
        byte[] messageData = frame.getReceiveBuffer();
        String deviceNumber = frame.getDeviceNumber();
        /* 网关回复,需要移除多余的网关标识*/
        if (msg.length > 8 && msg[8] == (byte) 0xE7) {
            int deviceLen = msg[10];
            byte[] deviceNo = new byte[deviceLen];
            System.arraycopy(msg, 11, deviceNo, 0, deviceLen);
            deviceNumber = new String(deviceNo, StandardCharsets.US_ASCII);
            int messageLength = msg.length - 11 - deviceLen;
            System.arraycopy(msg, 11 + deviceLen, messageData, 0, messageLength);
        }
        if (messageData[0] == (byte) 0xDB) {
            try {
                DeviceLogUtil.deviceLogger.logMessage(deviceNumber, DEVICE_TYPE_GATEWAY, MessageDirectEnum.DECRYPT_BEFORE, messageData );
                messageData = decodeReportData(messageData, getSecuritySuite(deviceNumber));
                DeviceLogUtil.deviceLogger.logMessage(deviceNumber, DEVICE_TYPE_GATEWAY, MessageDirectEnum.DECRYPT_AFTER, messageData );
            } catch (Exception e) {
                logger.error("decryptDataByte error", e);
            }
        }
        return messageData;
    }

    public static SecuritySuite getSecuritySuite(String deviceNo) {
        SecuritySuite securitySuite = new SecuritySuite();
        securitySuite.setAuthenticationMechanism(AuthenticationMechanism.HLS5_GMAC);
        securitySuite.setEncryptionMechanism(SecuritySuite.EncryptionMechanism.AES_GCM_128);
        securitySuite.setSecurityPolicy(SecuritySuite.SecurityPolicy.AUTHENTICATED_AND_ENCRYPTED);
        byte[] ak = new byte[]{0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32};
        byte[] ek = new byte[]{0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31};
        if (StringUtil.isNotEmpty(deviceNo)) {
            cn.com.netedge.iot.protocol.dlms.service.DeviceService deviceService = SpringContextUtil.getBean(MeterDeviceServiceImpl.class);
            MeterModel meterModel = (MeterModel) deviceService.getDeviceEntityByNo(deviceNo);
            if (meterModel != null && StringUtil.isNotEmpty(meterModel.getAk()) && StringUtil.isNotEmpty(meterModel.getEk())) {
                ak = meterModel.getAk().getBytes(StandardCharsets.US_ASCII);
                ek = meterModel.getEk().getBytes(StandardCharsets.US_ASCII);
            }
        }
        securitySuite.setAuthenticationKey(ak);
        securitySuite.setGlobalUnicastEncryptionKey(ek);
        return securitySuite;
    }

    public static byte[] decodeReportData(byte[] bytes, SecuritySuite securitySuite) throws IOException {
        try (DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes))) {
            byte[] ciphertext;
            byte[] plaintext = null;
            byte gtag = (byte) is.read();
            if (gtag == (byte) 0xDB) {
                AxdrLength axdrLength = new AxdrLength();
                axdrLength.decode(is);
                int encLength = axdrLength.getValue();
                byte[] tmpServerT = new byte[encLength];
                is.readFully(tmpServerT);
                axdrLength.decode(is);
                encLength = axdrLength.getValue();
                ciphertext = new byte[encLength];
                is.readFully(ciphertext);
                plaintext = GcmModule.decrypt(ciphertext, tmpServerT, securitySuite);
            }
            return plaintext;
        }
    }

    private static void putChannelId(String deviceNumber, int deviceType, String channelId) {
        //更新终端通道对应关系
        if (StringUtil.isEmpty(deviceNumber)) {
            logger.error("deviceNumber is empty");
            return;
        }
        String path = AppConstant.machineName + "/" + deviceNumber + "/" + deviceType;
        FeCommCacheUtil.setString(path, channelId);
        logger.trace("key:{} channelId:{} is set.", path, channelId);

    }

    private static DeviceInfo putDeviceInfo(String deviceNumber, int deviceType, String channelId) {
        if (StringUtil.isEmpty(deviceNumber)) {
            logger.error("deviceNumber is empty");
            return null;
        }
        //获取终端档案
        DeviceInfo deviceInfo = staticDeviceService.getDeviceInfo(deviceNumber, deviceType);
        if (deviceInfo == null) {
            logger.error("deviceNumber:{}, deviceType:{} , doc not exist", deviceNumber, deviceType);
            return null;
        }
        String channelRtuKey = CacheKeyConstants.KEY_DEVICE_INFO + channelId;
        FeCommCacheUtil.setString(channelRtuKey, JSON.toJSONString(deviceInfo));
        return deviceInfo;
    }

    private static DeviceInfo getDeviceInfo(String channelId) {
        String channelRtuKey = CacheKeyConstants.KEY_DEVICE_INFO + channelId;
        String deviceInfoJson = RedisUtil.get(channelRtuKey);
        if(StringUtil.isEmpty(deviceInfoJson)) {
            return null;
        }

       return JSON.parseObject(deviceInfoJson, DeviceInfo.class);
    }

    public static void updateDeviceRunInfo(Integer deviceId, String deviceNumber, int deviceType, String ip, boolean isLogin) {
        Date now = new Date();
        //最近一分钟设备上报次数
        String key = CacheKeyConstants.KEY_DEVICE_MESSAGE_COUNT + deviceNumber + "_" + deviceType + "_" + DateUtil.DateToNosec10Str(now);
        RedisUtil.incr(key);
        RedisUtil.expire(key, 60); // 1分钟

        key = CacheKeyConstants.KEY_DEVICE_RUNNING + deviceNumber + "_" + deviceType;
        String json = RedisUtil.get(key);
        DeviceRunningInfo info;
        if (StringUtil.isEmpty(json)) {
            logger.info("{} 's json is empty", key);
            info = new DeviceRunningInfo();
        } else {
            info = JSON.parseObject(json, DeviceRunningInfo.class);
            if (info == null) {
                logger.info("{}'s json:{} convert error.", key, json);
                info = new DeviceRunningInfo();
            }
        }
        if (isLogin) {
            info.setLastDataTime(now);
        }
        int oldStatus = info.getCurStatus();
        info.setMachineName(AppConstant.machineName);
        info.setDeviceId(deviceId);
        info.setDeviceNumber(deviceNumber);
        info.setDeviceType(deviceType);
        info.setOnlineTime(now);
        info.setCurStatus(OnlineStatusEnum.ONLINE.getCode());
        //更新至缓存
        FeCommCacheUtil.setString(key, JSON.toJSONString(info));

        if(oldStatus == OnlineStatusEnum.OFFLINE.getCode()) {
            DataCOnlineModel dataCOnlineModel = new DataCOnlineModel();
            dataCOnlineModel.setDeviceId(deviceId);
            dataCOnlineModel.setDeviceType(deviceType);
            dataCOnlineModel.setIsOnline(ONLINE);
            dataCOnlineModel.setIP(ip);
            dataCOnlineModel.setOccurTime(DateUtil.DateToLongStr(now));
            JmsMsgUtil.sendMessage(MqConstants.QUEUE_DATA_C_ONLINE_MESSAGE, JSON.toJSONString(dataCOnlineModel));
        }
    }
}
